#ifndef SIMPLE_IO_READ_ITERATOR_H
#define SIMPLE_IO_READ_ITERATOR_H

#include <cstddef> // std::byte std::ptrdiff_t std::size_t
#include <type_traits> // std::enable_if_t
#include <iterator> // std::input_iterator_tag

#include "handle.h" // handle interface operation
#include "read.h" // byte_range

namespace simple::io
{

	// NOTE: for non-blocking handles, if there is no more data to read, this
	// iterator will reach end, and afterwards can't be used again. To retry
	// reading a new instance must be created.
	// tried making it reusable by moving the reading to operator==, but that
	// makes it non-const which is a worse caveat IMHO, even if worked around
	// with mutable and such, it's just confusing to have comparison change
	// meaningful state of the object...
	class read_iterator
	{
		public:
		using value_type = std::byte;
		using difference_type = std::ptrdiff_t;
		using pointer = const std::byte*;
		using reference = const std::byte&;
		using iterator_category = std::input_iterator_tag;

		read_iterator() noexcept;
		read_iterator(const unique_handle&, byte_range buffer) noexcept;

		read_iterator(const read_iterator&) = delete;
		read_iterator& operator=(const read_iterator&) = delete;
		read_iterator(read_iterator&& other);
		read_iterator& operator=(read_iterator&&);

		template <operation allowed, std::enable_if_t<allowed && operation::read>* = nullptr>
		read_iterator(const interface<allowed>& i, byte_range buffer) noexcept
			: read_iterator(i.handle, buffer)
		{}

		read_iterator& operator++() noexcept;

		[[nodiscard]] reference operator*() noexcept;

		// [[nodiscard]] // cause std is dumb, what can I say
		// https://stackoverflow.com/questions/52332203/friend-member-functions-and-attributes-gcc-vs-clang
		friend bool operator==(const read_iterator& one, const read_iterator& other) noexcept;
		friend bool operator!=(const read_iterator& one, const read_iterator& other) noexcept;

		// for non-blocking handles, this can be used to check if we got to the
		// end of the stream, as opposed to only currently available data, also
		// always true for default constructed objects
		bool eof() const noexcept;

		int result_index() const noexcept; // in ex_read_result variant

		private:
		const enum handle handle;
		const byte_range buffer;

		std::byte* current;

		union ptr_or_index
		{
			std::byte* end;
			std::size_t index;

			ptr_or_index(std::size_t index) noexcept;
		} read_result;

		// TODO: consider making this public, to make it a bit easier to retry reading when non-blocking, see NOTE above
		void read_next() noexcept;
	};

	// TODO: read iterator with a large circular buffer and small block size (ala LZ compression), which is random access if you stay withing buffer and otherwise UB
	// TODO: read iterator with a large circular buffer and small block size, which is always random access based on file offsets, when accessing a random offset, if it's in the buffer just get it, if it's block size away from either end of the buffer, read the block in the appropriate position, otherwise read half block ahead and half block behind it into the buffer and forget what was there before

} // namespace simple::io

#endif /* end of include guard */
